<?php

    #-------------------------------------------------------------#
	# This file is part of the Tekuna MVC Microframework for PHP. #
	# (c) 2009 Niels Jäckel <niels.jaeckel@tekuna.org>            #
	# Please visit http://tekuna.org/ for more information.       #
	#                                                             #
	# This software is licensed under the MIT License. Please     #
	# view the LICENSE file for the full text of this license.    #
    #-------------------------------------------------------------#


	Tekuna :: load('org.tekuna.core.request.Request');
	Tekuna :: load('org.tekuna.core.response.Response');
	Tekuna :: load('org.tekuna.core.request.ClientInformation');
	Tekuna :: load('org.tekuna.core.request.URLInformation');
	Tekuna :: load('org.tekuna.core.filter.data.TekunaActionFilter');
	Tekuna :: load('org.tekuna.core.application.TekunaApplicationException');
	Tekuna :: load('org.tekuna.core.application.TekunaApplicationContext');
	Tekuna :: load('org.tekuna.core.application.DefaultLanguageChooser');
	Tekuna :: load('org.tekuna.core.application.DefaultModuleProcessor');
	Tekuna :: load('org.tekuna.core.application.DefaultLayoutComposer');
	Tekuna :: load('org.tekuna.core.config.ApplicationConfigurationBuilder');
	Tekuna :: load('org.tekuna.core.mvc.TekunaController');
	Tekuna :: load('org.tekuna.core.mvc.controller.AbstractMVCController');


	/**
	 * The TekunaApplication is the central controller for Tekuna
	 * applications. The needed configuration is loaded from a cetrain
	 * XML file.
	 */

	class TekunaApplication {

		protected
			$sApplicationConfigFile = '',
			$objApplicationContext = NULL,

			// controller helper objects
			$objLanguageChooser = NULL,
			$objModuleProcessor = NULL,
			$objLayoutComposer = NULL;



		// global application constants
		const

			/**
			 * The field in the GET scope that holds the action string
			 */

			ACTION_FIELD = 'action',


			/**
			 * The directory within the APPLICATION_ROOT directory
			 * that contains the layout templates
			 */

			BASEDIR_LAYOUTS = 'layouts/',



			/**
			 * The directory within the APPLICATION_ROOT directory
			 * that contains the fragment contents
			 */

			BASEDIR_FRAGMENTS = 'fragments/',



			/**
			 * The directory within the APPLICATION_ROOT directory
			 * that contains the MVC components
			 */

			BASEDIR_COMPONENTS = 'components/',



			/**
			 * The directory within the APPLICATION_ROOT directory
			 * that contains the language files
			 */

			BASEDIR_LANGFILES = 'lang/';


		/**
		 * TekunaApplication's constructor. A new application object is built on the basis
		 * of the given application configuration file name. The application context is
		 * initialized here.
		 *
		 * @param string $sApplicationConfigFile the relative path to the application config file
		 */

		public function __construct($sApplicationConfigFile) {

			// store the file name of the config file for possible later usage
			$this -> sApplicationConfigFile = APPLICATION_ROOT . $sApplicationConfigFile;

			// create context objects
			$objRequest = new Request();
			$objResponse = new Response();
			$objClientInformation = new ClientInformation();
			$objConfig = ApplicationConfigurationBuilder :: buildConfigurationFromXMLFile($this -> sApplicationConfigFile);

			// set the default response protocol in the response object
			$objResponse -> setResponseProtocol($objRequest -> getRequestProtocol());

			// build context object
			$this -> objApplicationContext = new TekunaApplicationContext();
			$this -> objApplicationContext -> setRequest($objRequest);
			$this -> objApplicationContext -> setResponse($objResponse);
			$this -> objApplicationContext -> setClientInformation($objClientInformation);
			$this -> objApplicationContext -> setApplicationConfiguration($objConfig);

			// initialize the controller helpers
			$this -> objLanguageChooser = new DefaultLanguageChooser($this -> objApplicationContext);
			$this -> objModuleProcessor = new DefaultModuleProcessor($this -> objApplicationContext);
			$this -> objLayoutComposer = new DefaultLayoutComposer();
		}


		/**
		 * Perform a request on the application.
		 *
		 * @param string $sActionURL the request URL for the action
		 */

		public function handleRequest($sActionURL) {

			// get the context object for multiple usage
			$objContext = $this -> getApplicationContext();

			// initialize the URL information
			$objURLInformation = new URLInformation();
			$objURLInformation -> setRequestURL($sActionURL);

			// put the relative base path in the context
			$objContext -> setContextValue('relativeBasePath', $objURLInformation -> getRelativeBasePath());

			// store the URL information object in the application context
			$objContext -> setURLInformation($objURLInformation);

			// get the application language
			$sLanguage = $this -> getLanguageChooser() -> chooseLanguage();
			$objContext -> setApplicationLanguage($sLanguage);

			// match the application module
			$objModule = $objContext -> getApplicationConfiguration() -> getModuleForURL($sActionURL);
			
			// enrich the URL information object
			$sModuleBaseURL = $objModule -> getBaseURL();
			$objURLInformation -> setModuleBaseURL($sModuleBaseURL);
			$objURLInformation -> setComponentURL((string) substr($sActionURL, strlen($sModuleBaseURL)));

			// set the module at the layout composer
			$this -> getLayoutComposer() -> setModule($objModule);

			// process module
			$arrSlotContents = $this -> getModuleProcessor() -> processModule($objModule);

			// mix in the context values
			$arrSlotContents = array_merge($arrSlotContents, array('context' => $objContext -> getLayoutValues()));

			// compose the layout
			$sOutput = $this -> getLayoutComposer() -> composeLayout($arrSlotContents);

			// add the output to the response
			$objContext -> getResponse() -> appendContent($sOutput);

			// sent the response
			$this -> sendResponse();
		}


		protected function sendResponse() {

			// send the response
			$this -> objApplicationContext -> getResponse() -> sendResponse();
		}


		/**
		 * Static class load method.
		 *
		 * @param string $sFullQualifiedClass the fully qualified class name e.g. org.tekuna.base.Tekuna for this class
		 */

		public static function load($sFullQualifiedClass) {

			// static instance of the class loader
			static $objApplicationClassLoader = NULL;

			// create instance of the class loader if not yet done
			if ($objApplicationClassLoader === NULL) {

				$objApplicationClassLoader = new TekunaClassLoader(APPLICATION_ROOT);
			}

			// deligate class loading
			$objApplicationClassLoader -> loadClass($sFullQualifiedClass);
		}


		/**
		 * Builds s a new instance of the partly qualified TekunaController.
		 * The given class identifier is prefixed with 'components.controller.'
		 * so that a given 'MyController' will result in a class load for
		 * class 'components.controller.MyController'.
		 *
		 *
		 * @param string $sController the controller class identifier
		 * @param TekunaApplicationContext $objContext the application context
		 * @return TekunaController instance
		 */

		public static function getControllerInstance($sController, TekunaApplicationContext $objContext) {

			// prepend common controller 'package'
			$sFullController = 'components.controller.'. $sController;

			// extract the class name
			$sControllerClass = $sController;
			if (strpos($sControllerClass, '.') !== FALSE) {

				// extract the last part of the path to be the class name
				$sControllerClass = substr($sControllerClass, strrpos($sControllerClass, '.') + 1);
			}

			try {

				// load the controller class
				TekunaApplication :: load($sFullController);
			}
			catch (TekunaClassLoadException $e) {

				throw new TekunaApplicationException("Controller class file not found.", $e);
			}

			// get a controller instance
			$objController = new $sControllerClass($objContext);

			// check controller class type
			if (! $objController instanceof TekunaController) {

				throw new TekunaApplicationException("The controller '$sController' must implement the TekunaController interface.");
			}

			return $objController;
		}


		/**
		 * Default implementation for the action extractor. The field 'action'
		 * (defined in TekunaApplication :: ACTION_FIELD) in the GET scope
		 * holds the current request action that is loaded and filtered here.
		 *
		 * @return string the loaded and filtered action, or (if not available) just /
		 */

		public function getTekunaAction() {

			// init the action
			$sAction = '/';

			// this is the field in the GET scope for searching
			$sField = TekunaApplication :: ACTION_FIELD;

			// get request object for multiple use
			$objRequest = $this -> getApplicationContext() -> getRequest();

			// get the request value from the Request object if available
			if ($objRequest -> isGETValueAvailable($sField)) {

				// set filter
				$objRequest -> setGETFilter($sField, new TekunaActionFilter());

				// request value
				$sAction = $objRequest -> getGETValue($sField);
			}

			// return the action
			return $sAction;
		}


		/**
		 * @return string returns the used application configuration file
		 */

		public function getApplicationConfigFile() {

			return $this -> sApplicationConfigFile;
		}


		/**
		 * @return TekunaApplicationContext returns the application context
		 */

		public function getApplicationContext() {

			return $this -> objApplicationContext;
		}


		/**
		 * @return TekunaLanguageChooser returns the currently used language chooser
		 */

		public function getLanguageChooser() {

			return $this -> objLanguageChooser;
		}


		/**
		 * @return TekunaModuleProcessor returns the currently used module processor
		 */

		public function getModuleProcessor() {

			return $this -> objModuleProcessor;
		}


		/**
		 * @return TekunaLayoutComposer returns the currently used layout composer
		 */

		public function getLayoutComposer() {

			return $this -> objLayoutComposer;
		}
	}


